// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
import 'package:shared_preferences_platform_interface/types.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group(MethodChannelSharedPreferencesStore, () {
    const channel = MethodChannel('plugins.flutter.io/shared_preferences');

    const flutterTestValues = <String, Object>{
      'flutter.String': 'hello world',
      'flutter.Bool': true,
      'flutter.Int': 42,
      'flutter.Double': 3.14159,
      'flutter.StringList': <String>['foo', 'bar'],
    };

    const prefixTestValues = <String, Object>{
      'prefix.String': 'hello world',
      'prefix.Bool': true,
      'prefix.Int': 42,
      'prefix.Double': 3.14159,
      'prefix.StringList': <String>['foo', 'bar'],
    };

    const nonPrefixTestValues = <String, Object>{
      'String': 'hello world',
      'Bool': true,
      'Int': 42,
      'Double': 3.14159,
      'StringList': <String>['foo', 'bar'],
    };

    final allTestValues = <String, Object>{};

    allTestValues.addAll(flutterTestValues);
    allTestValues.addAll(prefixTestValues);
    allTestValues.addAll(nonPrefixTestValues);

    late InMemorySharedPreferencesStore testData;

    final log = <MethodCall>[];
    late MethodChannelSharedPreferencesStore store;

    setUp(() async {
      testData = InMemorySharedPreferencesStore.empty();

      Map<String, Object?> getArgumentDictionary(MethodCall call) {
        return (call.arguments as Map<Object?, Object?>)
            .cast<String, Object?>();
      }

      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
          .setMockMethodCallHandler(channel, (MethodCall methodCall) async {
            log.add(methodCall);
            if (methodCall.method == 'getAll') {
              return testData.getAll();
            }
            if (methodCall.method == 'getAllWithParameters') {
              final Map<String, Object?> arguments = getArgumentDictionary(
                methodCall,
              );
              final prefix = arguments['prefix']! as String;
              Set<String>? allowSet;
              final allowList = arguments['allowList'] as List<dynamic>?;
              if (allowList != null) {
                allowSet = <String>{};
                for (final Object? key in allowList) {
                  allowSet.add(key! as String);
                }
              }
              return testData.getAllWithParameters(
                GetAllParameters(
                  filter: PreferencesFilter(
                    prefix: prefix,
                    allowList: allowSet,
                  ),
                ),
              );
            }
            if (methodCall.method == 'remove') {
              final Map<String, Object?> arguments = getArgumentDictionary(
                methodCall,
              );
              final key = arguments['key']! as String;
              return testData.remove(key);
            }
            if (methodCall.method == 'clear') {
              return testData.clear();
            }
            if (methodCall.method == 'clearWithParameters') {
              final Map<String, Object?> arguments = getArgumentDictionary(
                methodCall,
              );
              final prefix = arguments['prefix']! as String;
              Set<String>? allowSet;
              final allowList = arguments['allowList'] as List<dynamic>?;
              if (allowList != null) {
                allowSet = <String>{};
                for (final Object? key in allowList) {
                  allowSet.add(key! as String);
                }
              }
              return testData.clearWithParameters(
                ClearParameters(
                  filter: PreferencesFilter(
                    prefix: prefix,
                    allowList: allowSet,
                  ),
                ),
              );
            }
            final setterRegExp = RegExp(r'set(.*)');
            final Match? match = setterRegExp.matchAsPrefix(methodCall.method);
            if (match?.groupCount == 1) {
              final String valueType = match!.group(1)!;
              final Map<String, Object?> arguments = getArgumentDictionary(
                methodCall,
              );
              final key = arguments['key']! as String;
              final Object value = arguments['value']!;
              return testData.setValue(valueType, key, value);
            }
            fail('Unexpected method call: ${methodCall.method}');
          });
      store = MethodChannelSharedPreferencesStore();
      log.clear();
    });

    tearDown(() async {
      await testData.clear();
    });

    test('getAll', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);
      expect(await store.getAll(), flutterTestValues);
      expect(log.single.method, 'getAll');
    });

    test('getAllWithParameters with Prefix', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);
      expect(
        await store.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: 'prefix.')),
        ),
        prefixTestValues,
      );
      expect(log.single.method, 'getAllWithParameters');
    });

    test('getAllWithParameters with allow list', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);
      final Map<String, Object> data = await store.getAllWithParameters(
        GetAllParameters(
          filter: PreferencesFilter(
            prefix: 'prefix.',
            allowList: <String>{'prefix.Bool'},
          ),
        ),
      );
      expect(data.length, 1);
      expect(data['prefix.Bool'], true);
      expect(log.single.method, 'getAllWithParameters');
    });

    test('remove', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);
      expect(await store.remove('flutter.String'), true);
      expect(await store.remove('flutter.Bool'), true);
      expect(await store.remove('flutter.Int'), true);
      expect(await store.remove('flutter.Double'), true);
      expect(await testData.getAll(), <String, dynamic>{
        'flutter.StringList': <String>['foo', 'bar'],
      });

      expect(log, hasLength(4));
      for (final call in log) {
        expect(call.method, 'remove');
      }
    });

    test('setValue', () async {
      expect(await testData.getAll(), isEmpty);
      for (final String key in allTestValues.keys) {
        final Object value = allTestValues[key]!;
        expect(await store.setValue(key.split('.').last, key, value), true);
      }
      expect(await testData.getAll(), flutterTestValues);

      expect(log, hasLength(15));
      expect(log[0].method, 'setString');
      expect(log[1].method, 'setBool');
      expect(log[2].method, 'setInt');
      expect(log[3].method, 'setDouble');
      expect(log[4].method, 'setStringList');
    });

    test('clear', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);
      expect(await testData.getAll(), isNotEmpty);
      expect(await store.clear(), true);
      expect(await testData.getAll(), isEmpty);
      expect(log.single.method, 'clear');
    });

    test('clearWithParameters with Prefix', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);

      expect(
        await testData.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: 'prefix.')),
        ),
        isNotEmpty,
      );
      expect(
        await store.clearWithParameters(
          ClearParameters(filter: PreferencesFilter(prefix: 'prefix.')),
        ),
        true,
      );
      expect(
        await testData.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: 'prefix.')),
        ),
        isEmpty,
      );
    });

    test('getAllWithParameters with no Prefix', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);

      expect(
        await testData.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: '')),
        ),
        hasLength(15),
      );
    });

    test('clearWithNoPrefix', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);

      expect(
        await testData.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: '')),
        ),
        isNotEmpty,
      );
      expect(
        await store.clearWithParameters(
          ClearParameters(filter: PreferencesFilter(prefix: '')),
        ),
        true,
      );
      expect(
        await testData.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: '')),
        ),
        isEmpty,
      );
    });

    test('clearWithParameters with allow list', () async {
      testData = InMemorySharedPreferencesStore.withData(allTestValues);

      expect(
        await testData.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: 'prefix.')),
        ),
        isNotEmpty,
      );
      expect(
        await store.clearWithParameters(
          ClearParameters(
            filter: PreferencesFilter(
              prefix: 'prefix.',
              allowList: <String>{'prefix.String'},
            ),
          ),
        ),
        true,
      );
      expect(
        (await testData.getAllWithParameters(
          GetAllParameters(filter: PreferencesFilter(prefix: 'prefix.')),
        )).length,
        4,
      );
    });
  });
}
